Constructors, Destructores, and Assignment Operators

它们属于 non-C part of C++,这些操作是一个 class 的基本操作,几乎每一个 class 都需要实现。应用如下准则之前先确保遵从的是 non-C part of C++。因为一些在较为底层的代码使用构造函数和析构函数会减弱控制性。

Know what functions C++ silently writes and calls

Explcitly disallow the use of comiler-generated functions you do not want

情形:当类的要求与这些生成的函数相矛盾时(如不应该被复制)
古法:

Declare destructors virtual in polymorphic base classes

多态

此处的“多态”指类通过虚函数支持的的动态多态。
核心是:通过 base class 的统一接口来处理具有相同性质的不同类型的对象。
并不是所有 base class 都有多态的需求。

当 derived class 对象经由 base class 指针被删除,而 base class 带有 non-virtual 析构函数,结果是 UB。实际结果通常是部分销毁。

阻止继承

C++ 的 final 机制阻止继承

抽象类、纯虚函数

希望创建抽象类,但没有任何纯虚函数时,可以声明一个 pure virtual 析构函数,注意如果析构函数是纯虚函数那么它需要一个定义,因为虚构函数会在对象被销毁时被调用(类似于显式调用)。

Prevent Exceptions from leaving destructors

C++ 不禁止析构函数抛出异常,但不禁止。原因主要在于复杂性,如

解决方法:

应该如何界定异常?

Never call virtual functions during construction or destruction

首先直观上在基类构造函数中调用 base class 的虚函数时(该基类构造函数通常由子类的构造函数调用)子类还没有被构造好。析构同理。

实际上,在基类构造/析构函数中虚函数根本不会解析到子类,调用的仍然是基类的虚函数(解析工作是由 linker 执行的)。但是如果不是这两类函数,直接调用和显式的 this-> 调用均会触发正确的虚函数机制。

可以理解为C++标准从行为层面遵从了我们的理解并最大限度地调用尝试完成任务。

将多份类似的构造函数统一为一份,减少出错的概率。

替代方案:

Have assignment oerators return a reference to *this

支持连锁形式

Handle assignment to self in operator=

潜在的自我赋值是有可能发生的,如 a\[i] = a[j]; *px = *py 等。

常见实现:

// not self-assignment-safe, not exception-safe, cause the object data to be ill-formed
Class::operator=(const Class&rhs){
	delete p;
	p = new Resource(*rhs.p);
	return *this;
}
// self-assigment-safe, but not exception-safe, use identity test
Class::operator=(const Class&rhs){
	if (rhs == &this)
		return *this;
	delete p;
	p = new Resource(*rhs.p);
	return *this;
}
// self-assignment-safe, not exception-safe
Class::operator=(const Class&rhs){
	Resource *tmp = new Resource(*rhs.p);
	delete p;
	p = tmp;
	return *this;
}
// both safe
Class::operator=(const Class&rhs){
	/*
	optional, weigh the possibility of self collision and choose the better one
	if (rhs == &this)
		return *this;
	*/
	Resource *old = p;
	p = new Resource(&rhs.p);
	delete old;
	return *this;
}
// both safe, but sometimes not efficient(weight the indirection and the copying to decide). A simple way to achieve exception safety.
Class::operator=(const Class&rhs){
	Class tmp {rhs};
	std::swap(tmp, *this);
	return *this;
}
Class::operator=(Class rhs){
	std::swap(rhs, *this);
	return *this;
}

Copy all parts of an object

复制构造函数和复制赋值运算符的完整性。
前者会隐式调用基类的默认构造函数而不是复制构造函数,所以需要手动在 member initializer list 中调用基类复制构造函数;后者完全不会默认调用任何基类赋值操作符,也需要手动调用,注意顺序。
手动调用并不是一个个赋初值而是调用基类的复制构造函数/复制赋值运算符。